Não se preocupe com os códigos que você verá por aqui, eles são parte do trabalho para alcançar resultados, no entanto basta ler o texto e analisar tabelas e gráficos. Tudo é explicado conforme o resultado que os códigos apresentam.
No dia 29/10/2023, domingo chuvoso, foi realizada uma prova para o cargo de técnico administrativo conforme o Edital Nº 151/2023-PRH. A prova ocorreu na saudosa Universidade Estadual de Maringá e digo saudosa, pois foi ali que tive meu grau de Bacharelado em Música concedido há alguns anos atrás e foi uma época boa, ah!
Eu sinceramente achei que fosse me dar bem nessa prova e esse foi um dos fatores que me deixou extremamente nervoso, considerando que já bati na trave ao realizar outros concursos, mas esse realmente tinha matérias com as quais eu estava familiarizado. Eu mencionei o dia chuvoso, não que isso seja relevante para o trabalho que você vai ver a seguir, mas porque gostaria de ressaltar que esse aspecto piorou meu nervosismo devido ao trânsito que tive que enfrentar (nesse dia minha esposa dirigiu), mesmo que tivéssemos tempo hábil para chegar.... bem, não preciso dizer muito mais, se você é concurseiro ou se já passou por provas que poderiam decidir algo muito importante para você, mesmo na escola, então vai me compreender.
Eu fiz a prova, e português foi "uma coisa" sabe? Eu sempre me saio bem em português, mas eu soube dessa vez que não seria minha matéria. No entanto eu tinha certeza que havia gabaritado o restante, exceto por matemática, que é meu ponto mais fraco (e deve ser o de muita gente como veremos nas análises que fiz aqui).
O resultado preliminar de divulgação da nota da prova objetiva - após o período recursal, em que os candidatos, se pudessem, dariam "braçadas" na cara da banca examinadora a fim de que ela alterasse o gabarito de uma questão - foi divulgado em 22/11/2023 no Edital nº 351/2023-PRH - Resultado da Prova Objetiva. Com esse resultado em mãos, eu pensei:
"Bem, já que estou aprendendo análise de dados, vou praticar algumas ferramentas, pedir ajuda do chatGPT e fazer uma análise simples da posição preliminar em que cada candidato possivelmente estará a partir dos dados obtidos no edital com resultado preliminar, quem sabe comparo uma coisa ou outra mais?" E assim nasce esse trabalho.
Não vou dizer que compreendo todas as linhas de código presentes aqui, exatamente, no momento da entrega do projeto; pois meu foco estava na análise, no contar uma históra que eu já tinha em mente, mas posso dizer tenho ferramentas suficientes para poder estudar e compreender o que há aqui, pois tudo o que é apresentado vem de um escopo em que já estudei as noções básicas e sei como buscar, então esse projeto me serve como ferramenta de estudo, posso fazer a engenharia reversa de pontos que não domino e aprender com o que eu mesmo realizei.
O que mais me ajudou na concretização disso, foram os cursos (que realizei por meio do "financial aid application") no site COURSERA - IBM Applied AI Professional Certificate(dentre os cursos nessa trilha, já pude obter 4 certificados de aprendizagem), bem como os cursos gratuitos de engenharia de prompt da udemy (para lidar com o chatGPT) e no youtube, esses cursos ensinam a extrair o melhor da ferramenta de processamento de linguagem natural que é o "GPT".
Não posso afirmar que estou correto em tudo o que fiz aqui, isso é uma tentativa e uma realização para demonstrar o que eu poderia analisar num primeiro momento, é meu jeito de contar uma história sobre o desemprenho preliminar dos candidatos e peço perdão por eventuais inconsistências, bem como conto com a ajuda de quem sabe mais que eu para poder melhorar minhas técnicas.
Sobre os dados, é claro que durante o caminho nesse projeto eu pensava: "poxa! se eu tivesse acesso a cada alternativa que cada candidato respondeu em cada questão, poderia identificar pontos fracos e fortes mais específicos". Imagine então se pudéssemos saber o que cada candidato come no café da manhã, quem prepara as refeições dele, se trabalha, se somente estuda; quanta coisa poderíamos extrair. Mas tudo bem, deixo esse trabalho para quem buscou ou "fez" esses dados (se existirem) e com certeza saberá lidar com eles melhor do que eu, um mero aspirante a analista de dados.
Dadas essas considerações eu gostaria realmente de dizer que escrevi essa introdução por último no projeto. Eu sabia exatamente o que ia fazer antes de iniciar tudo aqui, mas eu não sabia a cara com que iria ficar exatamente, no entanto o final foi o que esperei.
Obrigado por embarcar nessa trilha comigo!
import pandas as pd
import matplotlib.pyplot as plt
import plotly.express as px
%pip install nbformat --upgrade
Requirement already satisfied: nbformat in c:\users\eflov\anaconda3\lib\site-packages (5.9.2)Note: you may need to restart the kernel to use updated packages. Requirement already satisfied: fastjsonschema in c:\users\eflov\anaconda3\lib\site-packages (from nbformat) (2.16.2) Requirement already satisfied: jsonschema>=2.6 in c:\users\eflov\anaconda3\lib\site-packages (from nbformat) (4.17.3) Requirement already satisfied: jupyter-core in c:\users\eflov\anaconda3\lib\site-packages (from nbformat) (5.3.0) Requirement already satisfied: traitlets>=5.1 in c:\users\eflov\anaconda3\lib\site-packages (from nbformat) (5.7.1) Requirement already satisfied: attrs>=17.4.0 in c:\users\eflov\anaconda3\lib\site-packages (from jsonschema>=2.6->nbformat) (22.1.0) Requirement already satisfied: pyrsistent!=0.17.0,!=0.17.1,!=0.17.2,>=0.14.0 in c:\users\eflov\anaconda3\lib\site-packages (from jsonschema>=2.6->nbformat) (0.18.0) Requirement already satisfied: platformdirs>=2.5 in c:\users\eflov\anaconda3\lib\site-packages (from jupyter-core->nbformat) (2.5.2) Requirement already satisfied: pywin32>=300 in c:\users\eflov\anaconda3\lib\site-packages (from jupyter-core->nbformat) (305.1)
ANEXO I DO EDITAL Nº151/2023-PRH
Vejamos os tópicos cobrados na prova:
LÍNGUA PORTUGUESA
MATEMÁTICA
CONHECIMENTOS DE INFORMÁTICA
CONHECIMENTOS BÁSICOS DE LEGISLAÇÃO E ESTATUTO DA CRIANÇA E DO ADOLESCENTE
A seguir estou apenas realizando algumas leituras básicas dos dados, sem análises ou insights
file = r'D:\Jeanco\Meus projetos\notas_prova_uem\resultado_final_csv.csv'
# Dados apresentados com todos os nomes de candidatos, matérias, etc
df = pd.read_csv(file, skiprows=1)
df
| INSCRIÇÃO | CANDIDATO | L.PORT | MAT | INF O | LEG | EST | NOTA | |
|---|---|---|---|---|---|---|---|---|
| 0 | 4859 | Abgail de Souza | 1,50 | 0,25 | 1,00 | 0,25 | 0,50 | 3,50 |
| 1 | 4751 | Ábia Morais Silva | 1,25 | 0,00 | 0,50 | 0,75 | 0,50 | 3,00 |
| 2 | 12493 | Abinair Trindade Miranda Valerio | 1,00 | 0,25 | 0,25 | 0,50 | 0,50 | 2,50 |
| 3 | 17346 | Abner Fellipe Beliato | 1,25 | 0,00 | 0,75 | 0,25 | 0,50 | 2,75 |
| 4 | 13377 | Abner Mariano | 2,75 | 1,00 | 1,00 | 2,00 | 0,50 | 7,25 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 9699 | 10446 | Zildeni Nunes Acacio Cassidori | 1,25 | 0,00 | 0,00 | 0,75 | 0,25 | 2,25 |
| 9700 | 19599 | Zilma Fernandes dos Santos Soares | 1,75 | 0,25 | 0,00 | 1,25 | 0,50 | 3,75 |
| 9701 | 12953 | Zingara Facco Rodrigues | 1,00 | 0,25 | 0,25 | 0,75 | 0,50 | 2,75 |
| 9702 | 8830 | Zuleica Silva Marques de Lima | 2,75 | 0,50 | 0,75 | 0,50 | 0,50 | 5,00 |
| 9703 | 4453 | Zuleika Shiraishi Kagueyama | 1,25 | 0,50 | 0,75 | 1,00 | 0,50 | 4,00 |
9704 rows × 8 columns
Abaixo vou realizar operações para renomear as colunas a fim de facilitar a manipulação dos dados
# Renomear as colunas
df.columns = ['Inscricao', 'Candidato', 'Port', 'MAT', 'INF', 'LEG', 'ECA', 'Nota Final']
# Padronizar os nomes para minúsculas e separar por _
df.columns = df.columns.str.lower().str.replace(' ', '_')
# Exibir as primeiras linhas para verificar as mudanças
# Visualizando os dados com colunas renomeadas
df
| inscricao | candidato | port | mat | inf | leg | eca | nota_final | |
|---|---|---|---|---|---|---|---|---|
| 0 | 4859 | Abgail de Souza | 1,50 | 0,25 | 1,00 | 0,25 | 0,50 | 3,50 |
| 1 | 4751 | Ábia Morais Silva | 1,25 | 0,00 | 0,50 | 0,75 | 0,50 | 3,00 |
| 2 | 12493 | Abinair Trindade Miranda Valerio | 1,00 | 0,25 | 0,25 | 0,50 | 0,50 | 2,50 |
| 3 | 17346 | Abner Fellipe Beliato | 1,25 | 0,00 | 0,75 | 0,25 | 0,50 | 2,75 |
| 4 | 13377 | Abner Mariano | 2,75 | 1,00 | 1,00 | 2,00 | 0,50 | 7,25 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 9699 | 10446 | Zildeni Nunes Acacio Cassidori | 1,25 | 0,00 | 0,00 | 0,75 | 0,25 | 2,25 |
| 9700 | 19599 | Zilma Fernandes dos Santos Soares | 1,75 | 0,25 | 0,00 | 1,25 | 0,50 | 3,75 |
| 9701 | 12953 | Zingara Facco Rodrigues | 1,00 | 0,25 | 0,25 | 0,75 | 0,50 | 2,75 |
| 9702 | 8830 | Zuleica Silva Marques de Lima | 2,75 | 0,50 | 0,75 | 0,50 | 0,50 | 5,00 |
| 9703 | 4453 | Zuleika Shiraishi Kagueyama | 1,25 | 0,50 | 0,75 | 1,00 | 0,50 | 4,00 |
9704 rows × 8 columns
Rapidamente e de maneira mais clara, vamos também dar uma olhada em quantas linhas e colunas há nessa tabela.
# Verificando quantidade de linhas e colunas
df.shape
(9704, 8)
O primeiro valor entre parênteses nos mostra a quantidade de linhas e o segundo nos mostra a quantidade de colunas
Agora vou verificar os tipos de dados, é uma prática comum na análise de dados. Basicamente preciso saber se estou lidando com números ou textos, mas essa é uma explicação muito simplificada, não se preocupe com essa parte
# Observando os tipos de dados
df.dtypes
inscricao int64 candidato object port object mat object inf object leg object eca object nota_final object dtype: object
Os resultados estão acima e não são bem os tipos de dados que eu espero, como eu disse, não se preocupe com essa parte. Vou realizar a conversão dos dados para os formatos que espero que tenham.
# Transformando os tipos de dados
cols_to_convert = ['port', 'mat', 'inf', 'leg', 'eca', 'nota_final']
# Função para converter valores para float ou manter inalterado se não for possível
def convert_to_float(value):
try:
return float(value.replace(',', '.')) # Substituir vírgula por ponto para evitar problemas
except (ValueError, AttributeError): # Lidar com AttributeError ao encontrar valores não string
return value
# Aplicar a função apenas nas colunas específicas
for column in cols_to_convert:
df[column] = df[column].apply(convert_to_float)
Vou falar de um jeito que o leigo entenda a etapa a seguir. Está sendo realizada uma verificação rápida para sabermos se em cada coluna existe algum valor nulo, e como podemos ver, temos "0"(zero) valores nulos, ou seja, não há qualquer coluna com "dados faltando". Deixando mais claro ainda, tome a coluna "candidato" como exemplo e veja que há um '0' ali... isso indica que realmente há valores em todas as linhas da coluna "candidato" (zero valores faltando, para ser mais redundante ainda)
# Verificando se há algum valor nulo na tabela
df.isnull().sum()
inscricao 0 candidato 0 port 0 mat 0 inf 0 leg 0 eca 0 nota_final 0 dtype: int64
Vamos "escrever" uma tabela que apresenta os dados estatísticos básicos. Considere que cada coluna representa uma matéria específica da prova e a nota final (soma de todas as notas em cada matéria) na última coluna. Basicamente a primeira coluna onde vemos count, mean, std, etc nos traz algumas infomações das quais quero destacar count, mean, min, max.
# Tabela para apresentar estatísticas básicas como média, mediana, máximo, mínimo etc
df[['port','mat','inf','leg','eca','nota_final']].describe()
| port | mat | inf | leg | eca | nota_final | |
|---|---|---|---|---|---|---|
| count | 9704.000000 | 9704.000000 | 9704.000000 | 9704.000000 | 9704.000000 | 9704.000000 |
| mean | 1.922352 | 0.420213 | 0.770945 | 0.954941 | 0.428329 | 4.496780 |
| std | 0.748732 | 0.335230 | 0.312252 | 0.444426 | 0.130710 | 1.383657 |
| min | 0.250000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.250000 |
| 25% | 1.250000 | 0.250000 | 0.500000 | 0.750000 | 0.250000 | 3.500000 |
| 50% | 1.750000 | 0.250000 | 0.750000 | 1.000000 | 0.500000 | 4.500000 |
| 75% | 2.500000 | 0.750000 | 1.000000 | 1.250000 | 0.500000 | 5.500000 |
| max | 4.500000 | 1.250000 | 1.250000 | 2.000000 | 0.500000 | 9.000000 |
Dê um tempo para analisar a tabela acima. Veja a coluna "nota_final", por exemplo. Para essa coluna temos "mean" (média) sendo 4.4, o que é uma nota realmente baixa. Tivemos 9704 candidatos, e a prova tinha um valor total de 10.0. Há muito mais informação possível de ser extraída dessa simples tabela estatística, mas foge da proposta desse projeto.
O código abaixo possibilita visualizar uma tabela apenas com as notas médias (mean) alcançadas por matéria
# Média das matérias
subjects = ['port', 'mat', 'inf', 'leg', 'eca']
means = df[subjects].mean()
print(means)
port 1.922352 mat 0.420213 inf 0.770945 leg 0.954941 eca 0.428329 dtype: float64
Antes de continuar vou criar uma cópia dos dados originais, apenas por segurança caso precise realizar alguma manipulação que possa alterar os dados originais, para casos como esse utilizarei a cópia criada:
# Criar uma cópia geral do DataFrame original
df_copy = df.copy()
Como eu disse, achei a prova de português bastante desafiadora, pessoal. Quando me deparei com aquele texto extenso de 170 linhas, repleto de termos peculiares como "de chofre" e "palejam", confesso que me senti um pouco fora da minha zona de conforto. O estilo de texto apresentado, mais próximo de um vestibular do que de um concurso, tornou a tarefa ainda mais complexa em minha opinião.
Mas agora, ao ter observado as médias, uma constatação interessante: a matéria em que tive mais dificuldade foi, surpreendentemente, aquela que obteve a maior média geral. Língua Portuguesa lidera, mesmo com a complexidade do texto.
Vamos, no entanto, analisar um ponto importante: a quantidade de questões. Se considerarmos a distribuição de notas de acordo com o número de questões em cada matéria, observamos que temas relacionados à prova tiveram notas distribuídas da seguinte forma:
Considere que os temas relacionados à prova tiveram a nota distribuída da seguinte maneira:
| CONTEÚDO | QUANTIDADE * VALOR = VALOR TOTAL DAS QUESTÕES |
|---|---|
| Língua Portuguesa | 20 x 0,25 = 5,00 |
| Matemática | 05 x 0,25 = 1,25 |
| Conhecimento de Informática | 05 x 0,25 = 1,25 |
| Conhecimento Básico de Legislação | 08 x 0,25 = 2,00 |
| Estatuto da Criança e do Adolescente | 02 x 0,25 = 0,50 |
| Total de questões | 40 x 0,25 = 10,0 |
Aqui temos algo a se considerar, e ao olhar para a quantidade de questões em termos percentuais, o panorama nos revela outros insights:
Vamos utilizar valores aproximados para facilitar a visualização.
Língua Portuguesa tem 20 questões, cada uma valendo 0.25 pontos, totalizando 5 pontos. A média nessa matéria foi de 1.9 que corresponde a 7,6 questões, portanto, pode ser interpretada como uma média de 38% do total possível de questões. A matéria ECA, por outro lado, tem 2 questões, cada uma valendo 0.25 pontos, totalizando 0.5 pontos. A média para essa matéria foi de aproximadamente 0.4, o que corresponde a 1,68 questões, o que por sua vez, representa 84% do total possível de questões.
Agora, quando comparamos diretamente essas porcentagens, fica evidente que a média em ECA é maior do que em Língua Portuguesa? Nãããoooooo, o que fica evidente é que a quantidade de acertos é maior, mas não a média. Essa abordagem percentual leva em conta a escala total possível de acertos em cada matéria e oferece uma compreensão mais intuitiva da performance dos alunos em relação ao número de questões em cada disciplina.
Então, voltando "às médias", a média de 1.9 em Língua Portuguesa é baixa, considerando exclusivamente o total de pontos possíveis (5,0), mas continua sendo a maior média no conjunto dos dados.
Curiosamente - ou não - , errei seis questões nessa disciplina, cometendo equívocos na transferência para o gabarito definitivo e também errei duas de matemática. Mesmo com esse desafio pessoal, a análise sugere que, de certa forma, os alunos estavam mais bem preparados para Língua Portuguesa do que eu inicialmente percebi, mas isso quando considero apenas a maior média dentre todas as matérias.
Agora, vamos falar um pouco mais do "chute". A questão do "chute" como causa da maior média em Língua Portuguesa não parece se sustentar. Isso se deve ao fato de que, mesmo ao chutar, a probabilidade de acertar todas as questões em um teste maior será menor do que acertar todas em um teste menor. Isso ocorre porque a probabilidade de acerto em cada questão é independente, e a probabilidade de acertar todas as questões diminui à medida que o número de questões aumenta. Portanto, não é correto afirmar que, ao chutar todas as questões, teríamos mais chances de obter uma média mais alta em um teste com mais questões do que em um teste com menos questões. Embora a aleatoriedade do chute possa levar a resultados diferentes, a probabilidade geral de obter uma média mais alta não é necessariamente maior em testes mais longos. Cada questão tem uma probabilidade independente de ser respondida corretamente ao acaso, e essa probabilidade é constante, independentemente do número total de questões.
Bem, caso ainda não tenha entendido, vamos visualizar com um gráfico:
def create_bar_chart(ax, labels, values, max_values, colors, title, xlabel, ylabel, legend_label):
# Iterar sobre as matérias
for i, label in enumerate(labels):
max_value = max_values[i]
value = values[i]
# Adicionar barra até a nota máxima possível
ax.bar(label, max_value, color='lightgray', label=legend_label if i == 0 else "")
# Adicionar barra preenchida até a altura correspondente à média
ax.bar(label, value, color=colors[i])
# Adicionar anotação sobre a nota máxima possível
ax.annotate(f'{max_value:.2f}', xy=(label, max_value), xytext=(0, 5),
textcoords='offset points', ha='center', va='bottom', color='black', fontsize=8)
# Adicionar rótulos e título
ax.set_title(title)
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
# Adicionar legenda
ax.legend()
# Dados do describe
data = {
'Matéria': ['Português', 'Mat', 'Inf', 'Leg', 'ECA'],
'Média': [1.922352, 0.420213, 0.770945, 0.954941, 0.428329],
}
# Dados da quantidade de questões e nota máxima possível por matéria
questions_max = {
'Matéria': ['Português', 'Mat', 'Inf', 'Leg', 'ECA'],
'Quantidade de Questões': [20, 5, 5, 8, 2],
'Nota Máxima Possível': [5.00, 1.25, 1.25, 2.00, 0.50],
}
# Gráfico de Barras para Médias
fig, ax = plt.subplots(figsize=(10, 5))
create_bar_chart(ax, data['Matéria'], data['Média'], questions_max['Nota Máxima Possível'],
['blue', 'orange', 'green', 'red', 'purple'], 'Média por Matéria', 'Matéria', 'Nota', 'Nota máxima possível')
plt.show()
Vamos entender juntos, de uma vez por todas, o que o gráfico acima nos esclarece mais ainda!
Cada cor representa a média geral alcançada por todos os candidatos, como já vimos antes no quadro de estatísticas básicas.
As barras cinzas? Bem, essas são como um lembrete, mostrando a nota máxima possível em cada matéria. Uma forma visual de entender como os candidatos se saíram em relação à média geral, considerando o máximo que poderiam atingir. Tudo claro até aqui?
Observe que em Português, temos a maior média do conjunto. Isso ocorre coincidentemente com o fato de ser a disciplina em que a pontuação máxima possível também era mais elevada, mas vale ressaltar que não há uma relação direta de causalidade entre esses dois fatores, quer dizer, o máximo que se poderia alcançar em português é 5,0 e essa é a nota mais alta que se poderia alcançar quando comparada com todas as matérias da prova.
# Dados da quantidade de questões por matéria
questions = {
'Matéria': ['Português', 'Mat', 'Inf', 'Leg', 'ECA'],
'Quantidade de Questões': [20, 5, 5, 8, 2],
}
# Calcular porcentagem de acertos com base na média e quantidade de questões
data['Porcentagem Média'] = [(m / (q * 0.25)) * 100 for m, q in zip(data['Média'], questions['Quantidade de Questões'])]
# Gráfico de Barras para Porcentagem de Acertos
fig, ax = plt.subplots(figsize=(10, 5))
bars = ax.bar(data['Matéria'], data['Porcentagem Média'], color=['blue', 'orange', 'green', 'red', 'purple'])
# Adicionar rótulos e título
ax.set_title('Porcentagem de Acertos por Matéria')
ax.set_xlabel('Matéria')
ax.set_ylabel('Porcentagem de Acertos')
# Adicionar valores acima das barras
for bar in bars:
yval = bar.get_height()
plt.text(bar.get_x() + bar.get_width()/2, yval, round(yval, 2), ha='center', va='bottom')
# Adicionar grade
ax.grid(axis='y', linestyle='--', alpha=0.7)
# Ajustar limites do eixo y
plt.ylim(0, 100)
plt.show()
Já para as porcentagens de acertos, como vimos, ECA sai disparado na frente em relação a Português (e a todas as outras matérias). Português fica "mais embaixo", né?
O gráfico realmente nos mostra o que já havíamos considerado anteriormente e nele há os valores exatos das porcentagens.
Uma outra questão que quero considerar é o quanto as matérias Português e Matemática estão dentre as que têm menores porcentagens de acertos. Vamos realçar uma discrepância entre a percepção comum e resultados reais:
"Faz essa prova aí do concurso, é matéria do ensino médio, tudo coisa fácil!!!" Já ouviu isso? eu já, muito!!!
Oras, porcentagens de acertos reveladas no gráfico nos surpreendem! Contrariando a percepção comum de que disciplinas como Português e Matemática, consideradas 'básicas' do ensino médio, seriam mais fáceis, os números nos mostram outra realidade. ECA, por exemplo, lidera com folga, demonstrando que nem sempre a familiaridade se traduz em sucesso. Matérias como essa, de legislação, muitas vezes subestimadas, revelaram-se nesse estudo como a matéria de maior desempenho.
Esses resultados apontam para algo como a importância de valorizar e se atualizar em todas as áreas, inclusive nas consideradas mais básicas. Afinal, a prova nos revela que o que pode parecer 'fácil' nem sempre corresponde à realidade das porcentagens de acertos
Devemos levar em conta, e não podemos nos esquecer que o número de questões para cada matéria era diferente; mas será que se a dificuldade das questões fossem mantidas, se os candidatos estivessem nas mesmas condições em que estavam no dia que realizaram a prova, e o número de questões fossem idênticos para cada matéria, o resultado seria muito diferente? Boa questão não é mesmo? E é totalmente coerente, mas não vamos nos ater a isso e deixamos para um outro trabalho, mas lembre-se que um conjunto de dados pode revelar muito mais e nos deixa aptos a explorar maiores e mais complexas questões.
Nesse instante estamos interessados nos "candidatos top". O edital previu 17 vagas sendo 14 para ampla concorrência, 1 para pessoa com deficiência e 2 para afro descendentes
Vamos desconsiderar as cotas, pois não temos como obter, com os dados utilizados para nossa análise, informações suficientes que possibilitem elencar os candidatos que possam concorrer a essas vaga; bem como vamos desconsiderar a idade como critério de desempate, os quais são os seguintes conforme o edital menciona:
Agora, a partir dos dados da tabela em mãos, iremos de fato investigar quais alunos tiveram notas mais altas e que poderiam ficar entre os 17 que ocupariam as vagas disponíveis. Precisamos seguir algumas etapas e vamos começar analisando quantos alunos têm nota maior ou igual a 6.0, por quê? Pois a nota para ser aprovado, segundo o edital precisa ser maior ou igual a 6.0, então todos os que tiverem notas dentro desse intervalo estão aprovados.
# Verificando quantidade de notas maiores ou iguais a 6.0
major_or_equal_6 = df['nota_final']>=6.0
major_or_equal_6.sum()
1735
São 1735 candidatos que tiveram essa nota, então precisamos verificar quais são as notas,os números mesmo, dentro desse intervalo:
# Verificando os valores únicos dentro do intervalo >=6.0
unique_values = df.loc[major_or_equal_6, 'nota_final'].sort_values(ascending=False)
unique_values.unique()
array([9. , 8.75, 8.5 , 8.25, 8. , 7.75, 7.5 , 7.25, 7. , 6.75, 6.5 ,
6.25, 6. ])
Vamos destacar o que temos ali:
9, 8.75, 8.5, 8.25, 8, 7.75, 7.5, 7.25, 7, 6.75, 6.5, 6.25, 6
Os valores apresentados são as notas únicas dentro do intervalo >=6, ou seja, precisamos agora saber a quantidade de cada nota, perceba que temos notas 9.0, 8.75 e assim por diante, mas ainda não sabemos quantos valores de cada temos, então vamos também verificar isso:
# Verificando a quantidade de cada nota única
counts = unique_values.value_counts().sort_values(ascending=True)
# Transformando a contagem de notas em um DataFrame
df_counts = pd.DataFrame(counts).reset_index()
df_counts.columns = ['nota_final', 'quantidade']
# Exibindo o DataFrame
df_counts
| nota_final | quantidade | |
|---|---|---|
| 0 | 9.00 | 2 |
| 1 | 8.75 | 2 |
| 2 | 8.50 | 8 |
| 3 | 8.25 | 22 |
| 4 | 8.00 | 47 |
| 5 | 7.50 | 64 |
| 6 | 7.75 | 71 |
| 7 | 7.25 | 140 |
| 8 | 7.00 | 178 |
| 9 | 6.75 | 194 |
| 10 | 6.50 | 265 |
| 11 | 6.25 | 357 |
| 12 | 6.00 | 385 |
Vou criar um gráfico em que o eixo horizontal representa cada nota e o vertical representa a quantidade de cada uma dessas notas, apenas para ficar mais visual.
# Criar gráfico de barras
plt.figure(figsize=(10, 6))
plt.bar(df_counts['nota_final'], df_counts['quantidade'], color='blue', width=0.2)
# Adicionar rótulos e título
plt.xlabel('Nota Final')
plt.ylabel('Quantidade')
plt.title('Frequência de Notas Finais')
# Adicionar valores acima das barras
for i, v in enumerate(df_counts['quantidade']):
plt.text(df_counts['nota_final'].iloc[i], v + 10, str(v), ha='center', va='bottom')
# Adicionar grade
plt.grid(axis='y', linestyle='--', alpha=0.7)
plt.show()
Como podemos ver na tabela (e gráfico) acima, temos 2 candidatos que atingiram nota 9.0 e 2 candidatos que atingiram nota 8.75. Apenas por curiosidade, num primeiro momento, vamos ver quem são esses candidatos, os nomes deles.
# Filtrando o DataFrame original para as notas 9.0 e 8.75
specific_note = df[df['nota_final'].isin([9.0, 8.75])]
# Exibindo apenas as colunas "candidato" e "nota_final"
result = specific_note[['candidato', 'port', 'mat', 'inf', 'leg', 'eca','nota_final']]
# Resetando o índice, se desejar
result = result.reset_index(drop=True)
# Exibindo o resultado
result.sort_values(by='nota_final', ascending=False)
| candidato | port | mat | inf | leg | eca | nota_final | |
|---|---|---|---|---|---|---|---|
| 2 | Jefferson Hyan Ferreira | 4.50 | 1.25 | 1.00 | 1.75 | 0.5 | 9.00 |
| 3 | Jonas Luís Rockenbach | 4.25 | 1.25 | 1.25 | 1.75 | 0.5 | 9.00 |
| 0 | Alan Christian Gimenez | 4.25 | 1.25 | 1.00 | 1.75 | 0.5 | 8.75 |
| 1 | Jackeline Santos Neves da Silva | 4.25 | 0.75 | 1.25 | 2.00 | 0.5 | 8.75 |
Com esse "pequeno conjunto de candidatos" fica fácil saber qual deles está em primeiro lugar de acordo com os critérios de desempate, no entanto quando começamos a avaliar as outras notas da tabela, o "trabalho braçal" pode não ser tão produtivo e muito menos intuitivo; então precisamos de uma abordagem mais sistemática que calcule as notas finais, os critérios de desempate e logo depois posicione os candidatos em uma lista. Vamos revisar os critérios de desempate com os quais estamos lidando:
def calculate_placement(df, cutoff_score=6.0):
# Criar cópia do dataframe original com notas iguais ou maiores que 6
higher_or_equal_scores = df[df['nota_final'] >= cutoff_score].copy()
# Calcular as notas com critério de desempate
higher_or_equal_scores['nota_final_with_tiebreak'] = (
higher_or_equal_scores['port'] +
higher_or_equal_scores['mat'] +
higher_or_equal_scores['inf'] +
higher_or_equal_scores['leg'] +
higher_or_equal_scores['eca']
)
# Ordenar as notas
higher_or_equal_scores = higher_or_equal_scores.sort_values(
by=['nota_final_with_tiebreak', 'nota_final', 'port', 'leg', 'mat'],
ascending=[False, False, False, False, False]
)
# Adicionar coluna colocação_preliminar
higher_or_equal_scores['colocacao_preliminar'] = range(1, len(higher_or_equal_scores) + 1)
# Setar a coluna "colocação-preliminar" como índice
higher_or_equal_scores.set_index('colocacao_preliminar', inplace=True)
# Selecionar as colunas a serem exibidas
final_result = higher_or_equal_scores[['candidato', 'port', 'mat', 'inf', 'leg', 'eca', 'nota_final']]
return final_result
# Usando a função
df_final_result = calculate_placement(df)
df_final_result
| candidato | port | mat | inf | leg | eca | nota_final | |
|---|---|---|---|---|---|---|---|
| colocacao_preliminar | |||||||
| 1 | Jefferson Hyan Ferreira | 4.50 | 1.25 | 1.00 | 1.75 | 0.5 | 9.00 |
| 2 | Jonas Luís Rockenbach | 4.25 | 1.25 | 1.25 | 1.75 | 0.5 | 9.00 |
| 3 | Jackeline Santos Neves da Silva | 4.25 | 0.75 | 1.25 | 2.00 | 0.5 | 8.75 |
| 4 | Alan Christian Gimenez | 4.25 | 1.25 | 1.00 | 1.75 | 0.5 | 8.75 |
| 5 | Naylor Moreira Batista | 4.25 | 0.75 | 1.00 | 2.00 | 0.5 | 8.50 |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 1731 | Matheus Luiz Pauka Siquieri | 1.75 | 0.75 | 1.25 | 1.75 | 0.5 | 6.00 |
| 1732 | Maycon Jose Marcelino | 1.75 | 0.75 | 1.25 | 1.75 | 0.5 | 6.00 |
| 1733 | Guilherme Cesar Ferreira Furtado | 1.75 | 1.25 | 1.00 | 1.50 | 0.5 | 6.00 |
| 1734 | Alexandre Carlos de Alencar Correa | 1.50 | 0.75 | 1.25 | 2.00 | 0.5 | 6.00 |
| 1735 | Gabriela de Almeida Barbosa | 1.50 | 1.00 | 1.25 | 1.75 | 0.5 | 6.00 |
1735 rows × 7 columns
Algo que não falamos antes: perceba que há um salto entre o valor 5 e 1731 (se você reparou bem, há esse mesmo "salto" em outras tabelas antes apresentadas). O programa de análise de dados nos dá apenas um vislumbre do total, mas acredite, todos os candidatos estão nesse conjunto ok? Agora sim temos uma tabela com a relação dos candidatos que tiveram notas maiores ou iguais a 6.0 bem como a colocação de cada um deles apresentada na primeira coluna, já considerados a maior nota final e o critério de desempate. Vamos elencar os 17 candidatos que ficaram no topo da lista.
df_top_17 = df_final_result.head(17)
df_top_17
| candidato | port | mat | inf | leg | eca | nota_final | |
|---|---|---|---|---|---|---|---|
| colocacao_preliminar | |||||||
| 1 | Jefferson Hyan Ferreira | 4.50 | 1.25 | 1.00 | 1.75 | 0.50 | 9.00 |
| 2 | Jonas Luís Rockenbach | 4.25 | 1.25 | 1.25 | 1.75 | 0.50 | 9.00 |
| 3 | Jackeline Santos Neves da Silva | 4.25 | 0.75 | 1.25 | 2.00 | 0.50 | 8.75 |
| 4 | Alan Christian Gimenez | 4.25 | 1.25 | 1.00 | 1.75 | 0.50 | 8.75 |
| 5 | Naylor Moreira Batista | 4.25 | 0.75 | 1.00 | 2.00 | 0.50 | 8.50 |
| 6 | Elisangela Pacheco da Silva | 4.25 | 0.50 | 1.25 | 2.00 | 0.50 | 8.50 |
| 7 | Yukiko Sakomoto Belem | 4.25 | 0.50 | 1.25 | 2.00 | 0.50 | 8.50 |
| 8 | Felipe Pfeffer | 4.25 | 1.00 | 1.00 | 1.75 | 0.50 | 8.50 |
| 9 | Thais Nicoletti Silva | 4.25 | 1.00 | 1.25 | 1.50 | 0.50 | 8.50 |
| 10 | Allan Sorrilha Meira Barros | 4.00 | 1.25 | 1.00 | 1.75 | 0.50 | 8.50 |
| 11 | Vanessa Carvalho Fenelon | 4.00 | 1.25 | 1.00 | 1.75 | 0.50 | 8.50 |
| 12 | Bruna Barbara Polizer | 4.00 | 1.00 | 1.25 | 1.75 | 0.50 | 8.50 |
| 13 | Mateus Henrique Aparecido Primilla | 4.25 | 0.50 | 1.25 | 2.00 | 0.25 | 8.25 |
| 14 | Pedro Henrique de Souza Marques | 4.25 | 1.00 | 1.00 | 1.50 | 0.50 | 8.25 |
| 15 | Renan Constantino Colli | 4.25 | 1.00 | 1.00 | 1.50 | 0.50 | 8.25 |
| 16 | Robson dos Santos Mendonça | 4.25 | 1.00 | 1.00 | 1.50 | 0.50 | 8.25 |
| 17 | Gabriel Weber Maximowski | 4.25 | 0.75 | 1.25 | 1.50 | 0.50 | 8.25 |
Aí acima, então, temos os nomes daqueles que vão trabalhar pro estado, ao menos segundo o resultado dessa análise da nota preliminar :).
Vou criar outro gráfico apenas para que possamos visualizar em que nível um candidato ficou em relação a outro conforme cada matéria. Cada barra representa uma matéria e o eixo vertical representa a nota de cada matéria (lembre-se que o limite máximo de nota para cada matéria é diferente, como já vimos num gráfico anterior)
O gráfico criado será interativo. Cada cor representa uma matéria específica, como se traduz no quadro indicativo ao lado do gráfico ("Disciplina"). Inicialmente, com as barras empilhadas, o gráfico não facilita muito a comparação do desempenho em cada matéria; no entanto, temos um "efeito": se navegarmos com nosso mouse sobre as barras veremos um quadro "flutuante" com o nome do candidato, o nome da disciplina e a nota alcançada. Já no quadro "Disciplina", na lateral do gráfico, podemos "esconder as disciplinas" clicando em qualquer das cores, assim ela desaparecerá do gráfico, e se clicarmos novamente na cor que desapareceu, ela aparecerá no gráfico.
Experimente deixar apenas uma cor visível, fica muito mais fácil comparar o desempenho entre os candidatos assim, veja lá e diga se não é.
def create_bar_chart(df, title, user_index=None, user_name=None):
# Selecionar as 17 primeiras linhas do DataFrame resultante
df_top_17 = df.head(17).copy()
# Adicionar uma linha para representar o desempenho do usuário, se aplicável
if user_index is not None:
df_user = df[df.index == user_index].copy()
df_user['candidato'] = user_name
df_combined = pd.concat([df_top_17, df_user])
else:
df_combined = df_top_17
# Criar um gráfico interativo com plotly
fig = px.bar(
df_combined,
x='candidato',
y=['port', 'mat', 'inf', 'leg', 'eca'],
title=title,
labels={'value': 'Nota', 'variable': 'Disciplina'},
hover_name='candidato',
template='plotly',
)
# Adicionar rótulos e título
fig.update_layout(
xaxis_title='Aluno',
yaxis_title='Nota',
yaxis=dict(visible=False) # Esconde o eixo y
)
# Ajustar o tamanho do quadro lateral
fig.update_layout(
margin=dict(l=50, r=20, t=40, b=20) # Ajustar as margens
)
# Aumentar a largura da barra do usuário, se aplicável
if user_index is not None:
df_combined.loc[df_combined.index == user_index, 'candidato'] = user_name
fig.update_traces(marker=dict(color='rgba(255, 0, 0, 0.7)'), selector=dict(name=user_name))
# Exibir o gráfico interativo
fig.show()
# Exibir o primeiro gráfico
create_bar_chart(df_top_17, 'Comparação do desempenho por matéria dos Top 17 candidatos')
Vamos ver em que posição eu fiquei na lista após os critérios de desempate serem aplicados:
df_final_result[df_final_result['candidato'] == 'Jeanco Mateus de Oliveira Volfe']
| candidato | port | mat | inf | leg | eca | nota_final | |
|---|---|---|---|---|---|---|---|
| colocacao_preliminar | |||||||
| 63 | Jeanco Mateus de Oliveira Volfe | 3.5 | 0.75 | 1.25 | 2.0 | 0.5 | 8.0 |
Minha colocação preliminar é 63.
Agora quero colocar também meu desempenho ao lado da galera que ficou no topo, não vai ser algo muito feliz de se fazer, maaas... hehe Aí vai, A ÚLTIMA COLUNA DO GRÁFICO representa meu desempenho
create_bar_chart(df_final_result, 'Notas por Disciplina para os Top 17 Alunos e Meu Desempenho em relação a todos eles', user_index=63, user_name='Jeanco Mateus de Oliveira Volfe')
Não posso dizer que bati na trave, pois eram poucas vagas comparado com minha posição preliminar (quem sabe eu faça um outro projeto com o resultado definitivo), mas com certeza estou evoluindo a cada prova que realizo e melhorando a cada dia de estudo, isso se reflete na nota que pude alcançar e na certeza que tenho, cada vez mais, e no não "chutar" em provas. Se sei, sei que sei; e se não sei, aí sim tenho que chutar(se for CESPE, dependendo, não dá - entendedores entenderão! rsrs), e se erro pensando que acertei estudarei para errar cada vez menos.
Parabéns a todos que realmente estudaram e concorreram nessa prova, e parabéns aos que conseguiram uma nota preliminar boa. Se você começou agora no mundo dos concursos e tirou uma nota muito baixa, posso dizer, não desanime, eu já estive aí e sei como é (e sei bem que cada um tem uma realidade distinta e muitas vezes "massacrante"); mas a cada resumo, a cada página lida, a cada "canetada" e prova, você está mais perto de ser "uma das primeiras barras" nos meus próximos gráficos que mostram desempenho...
Sejam (continuem) excelentes!
Enquanto escrevia esse trabalho pensei no quão privilegiado sou por existir nessa época com tantas tecnologias otimizadoras. Eu pude realizar essas análises simples em torno de 5 horas de trabalho, considerando o total de horas em dois dias em que trabalhei, mas devo reconhecer o poder de ferramentas como o chatGPT bem como a contribuição que a comunidade da análise de dados disponibiliza ao publicar vídeos gratuitos, melhorar documentações, ensinar... Um analista mais experiente poderá me dizer que isso é algo horrível, e eu não saberia dizer de fato o que é, ainda, horrível. Tenho buscado, lido sobre o assunto, perguntado, realizado cursos e estudo a cada dia um pouco para me aprimorar, portanto sou grato aos que podem contribuir com construções positivas.
O trabalho cumpre seu propósito em demonstrar alguns aspectos sobre notas, matérias, candidatos da prova e com certeza me abriu a mente para muito mais do que poderia ser realizado.
A análise realizada não apenas me proporcionou uma visão mais profunda sobre os resultados do concurso, mas também reforçou meu compromisso com o aprendizado contínuo. Ademais posso dizer que todos deveríamos ter um pouco de poder para analisar dados a fim de termos informações mais acertadas, pois é fascinante quando podemos descobrir algo simples que pensávamos ser de um jeito, mas que se revela de uma maneira diametralmente oposta quando fazemos uma abordagem honesta e "educada" utilizando dados.
Agradeço novamente à comunidade online, aos cursos valiosos e ao ChatGPT (OpenAI) por sua contribuição nesse percurso. À medida que avanço, estou ansioso para aplicar essas habilidades em desafios mais complexos e continuar explorando o vasto mundo da análise de dados. Que essa pequena caminhada trilhada aqui inspire outros a descobrir o poder da análise e a transformar dados em informações significativas que ensejem e inspirem a reflexão e a mudança que desejamos no mundo.